package compiler;

import interfaces.iCompilateur;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Locale;

import javax.jws.WebService;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import compiler.dto.Rapport;
import compiler.dto.RapportCompilation;
import compiler.dto.RapportExecution;

@WebService
public class Compiler implements iCompilateur {



	public Compiler() {
		super();
//		POUR WINDOWS - TODO : decommenter le setProperty
		System.setProperty("java.home", System.getenv("JAVA_HOME"));
	}

	/**
	 *  Emplacement pour stocker les .class
	 */
	private static String DOSSIERCLASSES = System.getProperty("java.io.tmpdir");

	/**
	 * Chemin vers la classe a executer.
	 */
	private static String CHEMIN = "com.toCheck";

	/** java File Object represents an in-memory java source file <br>
	 * so there is no need to put the source file on hard disk  **/
	private class InMemoryJavaFileObject extends SimpleJavaFileObject {
		private String contents = null;

		public InMemoryJavaFileObject(String className, String contents) throws Exception {
			super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
			this.contents = contents;
		}

		public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
			return contents;
		}
	}

	/** Get a simple Java File Object ,<br>
	 * It is just for demo, content of the source code is dynamic in real use case */
	private JavaFileObject getJavaFileObject(String fichier) {
		StringBuilder contents = new StringBuilder(fichier);
		JavaFileObject so = null;
		try {
			so = new InMemoryJavaFileObject(CHEMIN, contents.toString());
		}
		catch (Exception exception) {
			exception.printStackTrace();
		}
		return so;
	}

	/** 
	 * methode appelee par le webservice : permet de compiler du code et de recevoir un rapport de compilation.
	 */
	public RapportCompilation compile(String contenu) {
		// compilation de la chaîne
		RapportCompilation rapportCompilation = compileUneChaine(contenu);
		
		return rapportCompilation;
	}
	

	/**
	 * Cette methode permet de compiler du code contenu dans une chaine de caractere.
	 * @param contenu le code sous forme d'une chaine de caractere.
	 * @return un rapport de compilation : la compilation a reussie ou echouee, la liste des erreurs.
	 */
	private RapportCompilation compileUneChaine (String contenu) {
		
		// Construction d'un fichier source Java dans la memoire.
		JavaFileObject fichierSource = this.getJavaFileObject(contenu.toString());
		Iterable<? extends JavaFileObject> fichiersSources = Arrays.asList(fichierSource);

		RapportCompilation rapportCompilation = new RapportCompilation();

		// Recuperation du compilateur.
		JavaCompiler compilateur = ToolProvider.getSystemJavaCompiler();

		// Creation d'un diagnostic de compilation
		Diagnostiquer diagnostic = new Diagnostiquer(rapportCompilation);
		StandardJavaFileManager gestionnaire = compilateur.getStandardFileManager(diagnostic, Locale.ENGLISH, null);
		
		// Options pour la compilation telle que le dossier où stocker les .class
		Iterable options = Arrays.asList("-d", DOSSIERCLASSES);
		JavaCompiler.CompilationTask compilation = compilateur.getTask(null, gestionnaire,  diagnostic, options, null, fichiersSources);
		Boolean result = compilation.call();
		if (result == true)  {
			System.out.println("Compilation ok");
		} else {
			System.out.println("Probleme de compilation");
		}

		rapportCompilation.setReussiteCompilation(result);

		return rapportCompilation;
		
	}

	/**
	 * Cette methode permet de compiler et d'executer du code à partir d'une chaine de caractères.
	 */
	@Override
	public Rapport compileEtExecute(String contenu, String methode) {
		
		Rapport rapport = new Rapport();
		RapportCompilation rapportCompilation = this.compile(contenu); // compilation du code
		RapportExecution rapportExecution = new RapportExecution();
		
		if (rapportCompilation.isReussiteCompilation())
		{
			rapportExecution = this.execute(methode); // execution du code
		}
		
		rapport.setrCompilation(rapportCompilation);
		rapport.setrExecution(rapportExecution);

		return rapport;
	}
	
	/**
	 * Cette methode permet d'executer des .class
	 * @return la reussite ou l'echec de la compilation dans un rapport.
	 */
	private RapportExecution execute(String nomMethode) {
		
		RapportExecution rapportExecution = new RapportExecution();

		boolean reussiteExecution = true;
		boolean resultat = false;

		 File fichier = new File(DOSSIERCLASSES);
		
		try
		{
			URL url = fichier.toURL(); 
			URL[] urls = new URL[] { url };

			// Creation d'un chargeur de class a partir du dossier contenant les .class
			ClassLoader chargeurClasses = new URLClassLoader(urls);

			// Chargement de la classe a executer
			Class classeChargee = chargeurClasses.loadClass(CHEMIN);

			Class parametres[] = {};
			Object parametresObjet[] = {};
			Object instance = classeChargee.newInstance();
			Method[] methodes = classeChargee.getMethods();
			
			int bonneMethode = -1;
			for (int i = 0; i < methodes.length; i++) {
				if (methodes[i].getName().equals(nomMethode))
					bonneMethode = i;
			}

			if (bonneMethode != -1) {
				Method methodeTestee = classeChargee.getDeclaredMethod(methodes[bonneMethode].getName(), parametres);

				// lancement de la methode à tester sur l'instance de l'objet creee.
				Object resultatTest = methodeTestee.invoke(instance, parametresObjet);
				resultat = (Boolean) resultatTest;
				reussiteExecution = true;
			} else {
				reussiteExecution = false;
			}
			
		}
		catch (MalformedURLException e)  {
			reussiteExecution = false;
		}
		catch (ClassNotFoundException e) {
			reussiteExecution = false;
			System.out.println("Classe introuvable");
		}
		catch (Exception ex)  {
			reussiteExecution = false;
			rapportExecution.setStackTrace(ex.toString());
			ex.printStackTrace();
		}

		rapportExecution.setResultat(resultat);
		rapportExecution.setReussiteExecution(reussiteExecution);
		
		return rapportExecution;
		
	}
}
